// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Dart test program for testing FileSystemEntity.resolveSymbolicLinks

import "package:expect/expect.dart";
import "package:path/path.dart";
import "package:expect/async_helper.dart";
import 'dart:async';
import 'dart:io';

main() {
  String testsDir = Directory.current.uri.resolve('tests').toFilePath();

  // All of these tests test that resolveSymbolicLinks gives a path
  // that points to the same place as the original, and that it removes
  // all links, .., and . segments, and that it produces an absolute path.
  asyncTest(
    () => testFile(
      join(testsDir, 'standalone', 'io', 'resolve_symbolic_links_test.dart'),
    ),
  );
  asyncTest(
    () => testFile(
      join(
        testsDir,
        'standalone',
        'io',
        '..',
        'io',
        'resolve_symbolic_links_test.dart',
      ),
    ),
  );

  asyncTest(() => testDir(join(testsDir, 'standalone', 'io')));
  asyncTest(() => testDir(join(testsDir, 'lib', '..', 'standalone', 'io')));
  // Test a relative path.
  if (Platform.isWindows) {
    asyncTest(
      () => testFile(
        join(
          '\\\\?\\$testsDir',
          'standalone',
          'io',
          'resolve_symbolic_links_test.dart',
        ),
      ),
    );
    asyncTest(() => testDir('\\\\?\\$testsDir'));
  }
  asyncTest(
    () => Directory.systemTemp.createTemp('dart_resolve_symbolic_links').then((
      tempDir,
    ) {
      String temp = tempDir.path;
      return makeEntities(temp)
          .then(
            (_) => Future.wait([
              testFile(join(temp, 'dir1', 'file1')),
              testFile(join(temp, 'link1', 'file2')),
              testDir(join(temp, 'dir1', 'dir2', '..', '.', '..', 'dir1')),
              testDir(join(temp, 'dir1', 'dir2', '..', '.', '..', 'dir1')),
              testLink(join(temp, 'link1')),
              testDir('.'),
            ]),
          )
          .then((_) {
            if (Platform.isWindows) {
              // Windows applies '..' to a link without resolving the link first.
              return Future.wait([
                testFile(
                  join(
                    temp,
                    'dir1',
                    '..',
                    'link1',
                    '..',
                    'dir1',
                    'dir2',
                    'file2',
                  ),
                ),
                testDir(join(temp, 'dir1', '..', 'link1', '..', 'dir1')),
                testLink(join(temp, 'link1', '..', 'link1')),
              ]);
            } else {
              // Non-Windows platforms resolve the link before adding the '..'.
              return Future.wait([
                testFile(
                  join(temp, 'dir1', '..', 'link1', '..', 'dir2', 'file2'),
                ),
                testDir(join(temp, 'dir1', '..', 'link1', '..', 'dir2')),
                testLink(join(temp, 'link1', '..', '..', 'link1')),
              ]);
            }
          })
          .then((_) {
            Directory.current = temp;
            return Future.wait([
              testFile(
                'dir1/dir2/file2',
              ), // Test forward slashes on Windows too.
              testFile('link1/file2'),
              testFile(join('dir1', '..', 'dir1', '.', 'file1')),
              testDir('.'),
              testLink('link1'),
            ]);
          })
          .then((_) {
            Directory.current = 'link1';
            if (Platform.isWindows) {
              return Future.wait([
                testFile('file2'),
                // Windows applies '..' to a link without resolving the link first.
                testFile('..\\dir1\\file1'),
                testLink('.'),
                testDir('..'),
                testLink('..\\link1'),
              ]);
            } else {
              return Future.wait([
                testFile('file2'),
                // On non-Windows the link is changed to dir1/dir2 before .. happens.
                testFile('../dir2/file2'),
                testDir('.'),
                testDir('..'),
                testLink('../../link1'),
              ]);
            }
          })
          .whenComplete(() {
            Directory.current = testsDir;
            tempDir.delete(recursive: true);
          });
    }),
  );

  asyncTest(testNonExistantPath);
  asyncTest(testLinkTargetTypeChangedAfterCreation);
}

Future makeEntities(String temp) {
  return new Directory(join(temp, 'dir1', 'dir2'))
      .create(recursive: true)
      .then((_) => new File(join(temp, 'dir1', 'dir2', 'file2')).create())
      .then((_) => new File(join(temp, 'dir1', 'file1')).create())
      .then(
        (_) => new Link(join(temp, 'link1')).create(join(temp, 'dir1', 'dir2')),
      );
}

Future testFile(String name) {
  // We test that f.resolveSymbolicLinks points to the same place
  // as f, because the actual resolved path is not easily predictable.
  // The location of the temp directory varies from system to system,
  // and its path includes symbolic links on some systems.
  //Expect.isTrue(FileSystemEntity.identicalSync(name,
  //   new File(name).resolveSymbolicLinksSync()));
  return new File(name).resolveSymbolicLinks().then((String resolved) {
    //Expect.isTrue(FileSystemEntity.identicalSync(name, resolved));
    Expect.isTrue(isAbsolute(resolved));
    // Test that resolveSymbolicLinks removes all links, .., and . segments.
    Expect.isFalse(resolved.contains('..'));
    Expect.isFalse(resolved.contains('./'));
    Expect.isFalse(resolved.contains('link1'));
  });
}

Future testDir(String name) {
  Expect.isTrue(
    FileSystemEntity.identicalSync(
      name,
      new Directory(name).resolveSymbolicLinksSync(),
    ),
  );
  return new Directory(name).resolveSymbolicLinks().then((String resolved) {
    Expect.isTrue(FileSystemEntity.identicalSync(name, resolved));
    Expect.isTrue(isAbsolute(resolved));
    // Test that resolveSymbolicLinks removes all links, .., and . segments.
    Expect.isFalse(resolved.contains('..'));
    Expect.isFalse(resolved.contains('./'));
    Expect.isFalse(resolved.contains('link1'));
  });
}

Future testLink(String name) {
  Expect.isFalse(
    FileSystemEntity.identicalSync(
      name,
      new Link(name).resolveSymbolicLinksSync(),
    ),
  );
  Expect.isTrue(
    FileSystemEntity.identicalSync(
      new Link(name).targetSync(),
      new Link(name).resolveSymbolicLinksSync(),
    ),
  );
  return new Link(name).resolveSymbolicLinks().then((String resolved) {
    Expect.isFalse(FileSystemEntity.identicalSync(name, resolved));
    Expect.isTrue(isAbsolute(resolved));
    // Test that resolveSymbolicLinks removes all links, .., and . segments.
    Expect.isFalse(resolved.contains('..'));
    Expect.isFalse(resolved.contains('./'));
    Expect.isFalse(resolved.contains('link1'));
    return new Link(name)
        .target()
        .then((targetName) => FileSystemEntity.identical(targetName, resolved))
        .then((identical) => Expect.isTrue(identical));
  });
}

Future testNonExistantPath() async {
  try {
    await File('/tmp/foo/doesnotexist').resolveSymbolicLinks();
    Expect.fail("expected FileSystemException");
  } on FileSystemException {}
}

Future testLinkTargetTypeChangedAfterCreation() async {
  // Test the following scenario:
  // 1. create a file
  // 2. create a link to that file
  // 3. replace the file with a directory
  // 4. attempt to resolve the link
  final tmp = await Directory.systemTemp.createTemp(
    'dart_resolve_symbolic_links',
  );
  final tmpPath = tmp.absolute.path;
  final filePath = join(tmpPath, "file");
  final linkPath = join(tmpPath, "link");
  await File(filePath).create();
  await Link(linkPath).create(filePath);

  Expect.isTrue(
    FileSystemEntity.identicalSync(
      filePath,
      await Directory(linkPath).resolveSymbolicLinks(),
    ),
  );

  await File(filePath).delete();
  await Directory(filePath).create();

  if (Platform.isWindows) {
    await asyncExpectThrows<PathAccessException>(
      Directory(linkPath).resolveSymbolicLinks(),
    );
  } else {
    Expect.isTrue(
      await FileSystemEntity.identical(
        filePath,
        await Directory(linkPath).resolveSymbolicLinks(),
      ),
    );
  }
}
